Passed
Pull Request — master (#136)
by
unknown
01:56
created

ID3Helpers.ts ➔ isFrameDiscarded   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 7
c 0
b 0
f 0
rs 10
cc 2
1
import {
2
    FRAME_IDENTIFIERS,
3
    FRAME_ALIASES
4
} from "./definitions/FrameIdentifiers"
5
import * as ID3Frames from './ID3Frames'
6
import * as ID3Util from './ID3Util'
7
import { Frame } from './Frame'
8
import { getFrameSize } from './FrameHeader'
9
import { Options } from "./types/Options"
10
import { Tags, RawTags, WriteTags } from './types/Tags'
11
import { isKeyOf } from "./util"
12
13
/**
14
 * Returns array of buffers created by tags specified in the tags argument
15
 */
16
function createBuffersFromTags(tags: WriteTags) {
17
    const frames: Buffer[] = []
18
    if(!tags) {
19
        return frames
20
    }
21
    const rawObject = Object.entries(tags).reduce<RawTags>((acc, [key, value]) => {
22
        if (isKeyOf(key, FRAME_IDENTIFIERS.v3)) {
23
            acc[FRAME_IDENTIFIERS.v3[key]] = value
24
        } else if (isKeyOf(key, FRAME_IDENTIFIERS.v4)) {
25
            // Currently, node-id3 always writes ID3 version 3.
26
            // However, version 3 and 4 are very similar, and node-id3
27
            // can also read version 4 frames.
28
            // Until version 4 is fully supported, as a workaround,
29
            // allow writing version 4 frames into a version 3 tag.
30
            // If a reader does not support a v4 frame, it's (per spec)
31
            // supposed to skip it, so it should not be a problem.
32
            acc[FRAME_IDENTIFIERS.v4[key]] = value
33
        } else {
34
            acc[key as keyof RawTags] = value
35
        }
36
        return acc
37
    }, {})
38
39
    Object.entries(rawObject).forEach(([frameIdentifier, data]) => {
40
        // Check if invalid frameIdentifier
41
        if (frameIdentifier.length !== 4) {
42
            return
43
        }
44
        let frame
45
        if (isKeyOf(frameIdentifier, ID3Frames.Frames)) {
46
            frame = ID3Frames.Frames[frameIdentifier].create(data)
47
        } else if (frameIdentifier.startsWith('T')) {
48
            frame = ID3Frames.GENERIC_TEXT.create(frameIdentifier, data)
49
        } else if (frameIdentifier.startsWith('W')) {
50
            if (
51
                ID3Util.getSpecOptions(frameIdentifier).multiple &&
52
                data instanceof Array &&
53
                data.length
54
            ) {
55
                // deduplicate array
56
                const frames = [...new Set(data)].map(
57
                    url => ID3Frames.GENERIC_URL.create(frameIdentifier, url)
58
                ).filter((frame): frame is Buffer => !!frame)
59
                frame = Buffer.concat(frames)
60
            } else {
61
                frame = ID3Frames.GENERIC_URL.create(frameIdentifier, data)
62
            }
63
        }
64
        if (frame && frame instanceof Buffer) {
65
            frames.push(frame)
66
        }
67
    })
68
69
    return frames
70
}
71
72
/**
73
 * Return a buffer with the frames for the specified tags
74
 */
75
export function createBufferFromTags(tags: WriteTags) {
76
    return Buffer.concat(createBuffersFromTags(tags))
77
}
78
79
export function getTagsFromBuffer(buffer: Buffer, options: Options) {
80
    const framePosition = ID3Util.getFramePosition(buffer)
81
    if(framePosition === -1) {
82
        return getTagsFromFrames([], 3, options)
83
    }
84
    const frameSize = ID3Util.decodeSize(buffer.subarray(framePosition + 6, framePosition + 10)) + 10
85
    const frame = Buffer.alloc(frameSize + 1)
86
    buffer.copy(frame, 0, framePosition)
87
    //ID3 version e.g. 3 if ID3v2.3.0
88
    const version = frame[3]
89
    const tagFlags = ID3Util.parseTagHeaderFlags(frame)
90
    let extendedHeaderOffset = 0
91
    if(tagFlags.extendedHeader) {
92
        if(version === 3) {
93
            extendedHeaderOffset = 4 + buffer.readUInt32BE(10)
94
        } else if(version === 4) {
95
            extendedHeaderOffset = ID3Util.decodeSize(buffer.subarray(10, 14))
96
        }
97
    }
98
    const frameBody = Buffer.alloc(frameSize - 10 - extendedHeaderOffset)
99
    buffer.copy(frameBody, 0, framePosition + 10 + extendedHeaderOffset)
100
101
    const frames = getFramesFromTagBody(frameBody, version, options)
102
103
    return getTagsFromFrames(frames, version, options)
104
}
105
106
function isFrameDiscarded(frameIdentifier: string, options: Options) {
107
    if(options.exclude instanceof Array && options.exclude.includes(frameIdentifier)) {
108
        return true
109
    }
110
111
    return options.include instanceof Array && !options.include.includes(frameIdentifier)
112
}
113
114
function getFramesFromTagBody(tagBody: Buffer, version: number, options = {}) {
115
    if(!(tagBody instanceof Buffer)) {
116
        return []
117
    }
118
119
    const frames = []
120
    while(tagBody.length && tagBody[0] !== 0x00) {
121
        const frameSize = getFrameSize(tagBody, version)
122
123
        // Prevent errors due to broken data.
124
        if (frameSize > tagBody.length) {
125
            break
126
        }
127
128
        const frameBuffer = tagBody.subarray(0, frameSize)
129
        const frame = Frame.createFromBuffer(frameBuffer, version)
130
        if(frame && !isFrameDiscarded(frame.identifier, options)) {
131
            frames.push(frame)
132
        }
133
134
        tagBody = tagBody.subarray(frameSize)
135
    }
136
    return frames
137
}
138
139
function getTagsFromFrames(
140
    frames: Frame[],
141
    _version: number,
142
    options: Options = {}
143
) {
144
    const tags: Tags = {}
145
    const raw: RawTags = {}
146
147
    const push = (dest: unknown, value: unknown) => {
148
        const destArray = Array.isArray(dest) ? dest : []
149
        destArray.push(value)
150
        return destArray as never
151
    }
152
    const set = (_dest: unknown, value: unknown) => value as never
153
154
    frames.forEach(frame => {
155
        const identifier = frame.identifier as keyof RawTags
156
        const frameAlias = FRAME_ALIASES.v34[identifier] as keyof Tags
157
158
        const assign = ID3Util.getSpecOptions(identifier).multiple ? push : set
159
160
        if (!options.onlyRaw) {
161
            tags[frameAlias] = assign(tags[frameAlias], frame.getValue())
162
        }
163
        if (!options.noRaw) {
164
            raw[identifier] = assign(raw[identifier], frame.getValue())
165
        }
166
    })
167
168
    if (options.onlyRaw) {
169
        return raw
170
    }
171
    if (options.noRaw) {
172
        return tags
173
    }
174
175
    tags.raw = raw
176
    return tags
177
}
178
179
export function getTagsFromID3Body(body: Buffer) {
180
    return getTagsFromFrames(getFramesFromTagBody(body, 3), 3)
181
}
182